package com.ang.encrypt;

import cn.hutool.core.util.StrUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

/**
 * SM4加解密工具类
 * SM4（对称）与其它算法的比较
 * 国际上通过的对称加密算法是DES/AES算法
 * SM4在进行分组加密的时候进行了非线性变换
 */
public class SM4Util {

    private static final Logger log = LoggerFactory.getLogger(SM4Util.class.getSimpleName());

    private static final String ENCODING = "UTF-8";
    public static final String ALGORIGTHM_NAME = "SM4";
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
    public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
    public static final int DEFAULT_KEY_SIZE = 128;
    private static final int ENCRYPT_MODE = Cipher.ENCRYPT_MODE;
    private static final int DECRYPT_MODE = Cipher.DECRYPT_MODE;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生成key
     *
     * @return key
     * @date 2021/2/5 13:04
     **/
    public static byte[] generateKey() {
        try {
            return generateKey(EncryptDecrypt.DEFAULT_PRIVATE_KEY, DEFAULT_KEY_SIZE);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 根据传入的秘钥生成key
     *
     * @param paramKey 参数key
     * @return key
     * @date 2021/2/5 13:04
     **/
    public static byte[] generateKey(String paramKey) {
        try {
            return generateKey(paramKey, DEFAULT_KEY_SIZE);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 根据传入的秘钥生成key
     *
     * @param paramKey 参数key
     * @param keySize  key大小
     * @return key
     * @date 2021/2/5 13:04
     **/
    public static byte[] generateKey(String paramKey, int keySize) throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORIGTHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        if (StrUtil.isNotBlank(paramKey)) {
            random.setSeed(paramKey.getBytes());
        }
        kg.init(keySize, random);
        return kg.generateKey().getEncoded();
    }

    /**
     * ecb 加密
     *
     * @param key  秘钥key
     * @param data 数据
     * @return 加密后的数据
     * @date 2021/2/5 13:07
     **/
    public static byte[] encryptEcbPadding(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateEcbCipher(ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    /**
     * ecb 解密
     *
     * @param key        秘钥key
     * @param cipherData 数据
     * @return 加密后的数据
     * @date 2021/2/5 13:07
     **/
    public static byte[] decryptEcbPadding(byte[] key, byte[] cipherData) throws Exception {
        Cipher cipher = generateEcbCipher(DECRYPT_MODE, key);
        return cipher.doFinal(cipherData);
    }

    /**
     * 解密补位
     *
     * @param key        秘钥
     * @param iv         向量
     * @param cipherData 解密后的字符换
     * @return 解密补位
     * @date 2021/2/5 13:01
     **/
    public static byte[] decryptCbcPadding(byte[] key, byte[] iv, String cipherData) throws Exception {
        byte[] cipherBytes = Base64.decode(cipherData);
        Cipher cipher = generateCbcCipher(DECRYPT_MODE, key, iv);
        return cipher.doFinal(cipherBytes);
    }

    /**
     * 生成ecb密码
     *
     * @param mode 模式 1加密 2解密
     * @param key  秘钥字节
     * @return ecb密码
     * @date 2021/2/5 12:58
     **/
    private static Cipher generateEcbCipher(int mode, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }

    /**
     * 生成Cbc密码
     *
     * @param mode 模式 1加密 2解密
     * @param key  秘钥字节
     * @param iv   向量
     * @return Cbc密码
     * @date 2021/2/5 12:58
     **/
    private static Cipher generateCbcCipher(int mode, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_CBC_PADDING, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(mode, sm4Key, ivParameterSpec);
        return cipher;
    }

    /**
     * cbc 加密
     *
     * @param data       加密前的字符串
     * @param privateKey 十六进制key
     * @return 加密后的字符串
     * @date 2021/2/4 16:31
     **/
    public static String encryptEcbBase64(String data, String privateKey) {
        byte[] key = Hex.decode(privateKey);
        byte[] bytes = data.getBytes();
        try {
            bytes = encryptEcbPadding(key, bytes);
            data = Base64.toBase64String(bytes);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        return data;

    }

    /**
     * Ecb 解密
     *
     * @param cipherData 加密后的字符串
     * @param privateKey 十六进制key
     * @return 解密后的字符串
     * @date 2021/2/4 16:31
     **/
    public static String decryptEcbBase64(String cipherData, String privateKey) {
        byte[] bytes = Base64.decode(cipherData);
        byte[] key = Hex.decode(privateKey);
        try {
            bytes = decryptEcbPadding(key, bytes);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        cipherData = new String(bytes);
        return cipherData;
    }

    /**
     * cbc 加密
     *
     * @param data       加密前的字符串
     * @param privateKey 十六进制key
     * @return 加密后的字符串
     * @date 2021/2/4 16:31
     **/
    public static String encryptCbcBase64(String data, String privateKey) {
        try {
            byte[] iv = generateKey();
            byte[] key = generateKey(privateKey);
            byte[] bytes = data.getBytes();
            Cipher cipher = generateCbcCipher(ENCRYPT_MODE, key, iv);
            bytes = cipher.doFinal(bytes);
            data = Base64.toBase64String(bytes);
            return data;
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 解密
     *
     * @param cipherData 解密的字符串
     * @param privateKey key
     * @return 解密后的字符串
     * @date 2021/2/4 16:31
     **/
    public static String decryptCbcBase64(String cipherData, String privateKey) {
        byte[] iv = generateKey();
        byte[] bytes = Base64.decode(cipherData);
        byte[] key = generateKey(privateKey);
        try {
            Cipher cipher = generateCbcCipher(DECRYPT_MODE, key, iv);
            bytes = cipher.doFinal(bytes);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        cipherData = new String(bytes);
        return cipherData;
    }

    /**
     * 加密
     *
     * @param data       加密前的字符串
     * @param privateKey 十六进制key
     * @param charset    字符集
     * @return 加密后的字符串
     * @date 2021/2/4 16:31
     **/
    public static String encryptEcbHex(String data, String privateKey, String charset) {
        String cipherText = "";
        if (StrUtil.isNotBlank(data)) {
            byte[] keyData = ByteUtils.fromHexString(privateKey);
            charset = charset.trim();
            if (charset.length() <= 0) {
                charset = ENCODING;
            }
            try {
                byte[] srcData = data.getBytes(charset);
                byte[] cipherArray = encryptEcbPadding(keyData, srcData);
                cipherText = ByteUtils.toHexString(cipherArray);
            } catch (Exception e) {
                log.error("异常信息:{}", e.getMessage(), e);
            }
        }
        return cipherText;
    }

    /**
     * sm4解密十六进制
     *
     * @param cipherData 加密后的字符串
     * @param privateKey 十六进制key
     * @param charset    字符集
     * @return 解密后的字符串
     * @date 2021/2/4 16:31
     **/
    public static String decryptEcbHex(String cipherData, String privateKey, String charset) {
        byte[] keyData = ByteUtils.fromHexString(privateKey);
        byte[] cipherDataByte = ByteUtils.fromHexString(cipherData);
        try {
            byte[] srcData = decryptEcbPadding(keyData, cipherDataByte);
            if (StrUtil.isBlank(charset)) {
                charset = ENCODING;
            }
            return new String(srcData, charset);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 密码校验
     *
     * @param privateKey 密码key,16进制密钥（忽略大小写）
     * @param cipherData 加密后的字符串,16进制加密后的字符串
     * @param data       加密前的字符串,加密前的字符串
     * @return 校验结果
     * @date 2021/2/4 16:27
     **/
    public static boolean verifyEcb(String data, String privateKey, String cipherData) {
        byte[] keyData = ByteUtils.fromHexString(privateKey);
        byte[] cipherDataByte = ByteUtils.fromHexString(cipherData);
        try {
            byte[] decryptData = decryptEcbPadding(keyData, cipherDataByte);
            byte[] srcData = data.getBytes(ENCODING);
            return Arrays.equals(decryptData, srcData);
        } catch (Exception e) {
            log.error("异常信息:{}", e.getMessage(), e);
        }
        return false;
    }

    public static void main(String[] args) {
        String privateKey = EncryptDecrypt.DEFAULT_PRIVATE_KEY;
        String data = "这是一段原文，this is an original text.";
        String encodeBase64Str = encryptEcbBase64(data, privateKey);
        log.debug(">>>>>>>>>>>>>encryptEcbBase64:" + encodeBase64Str);
        String decodeBase64Str = decryptEcbBase64(encodeBase64Str, privateKey);
        log.debug(">>>>>>>>>>>>>decryptEcbBase64:" + decodeBase64Str);

        log.debug(">>>>>>>>>>>>>华丽的分割线<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        String encodeHexStr = encryptEcbHex(data, privateKey, ENCODING);
        log.debug(">>>>>>>>>>>>>encryptEcbHex:" + encodeHexStr);
        String decodeHexStr = decryptEcbHex(encodeHexStr, privateKey, ENCODING);
        log.debug(">>>>>>>>>>>>>decryptEcbHex:" + decodeHexStr);

        log.debug(">>>>>>>>>>>>>华丽的分割线<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        String encryptCbcBase64 = encryptCbcBase64(data, privateKey);
        log.debug(">>>>>>>>>>>>>encryptCbcBase64:" + encryptCbcBase64);
        String decryptCbcBase64 = decryptCbcBase64(encryptCbcBase64, privateKey);
        log.debug(">>>>>>>>>>>>>decryptCbcBase64:" + decryptCbcBase64);

        log.debug(">>>>>>>>>>>>>verifyEcb:{}", verifyEcb(data, privateKey, encodeHexStr));

    }
}
